import sys
import tabs
import time
from queue import Queue
#from PyQt4.QtGui import QWidget,QPainter,QMainWindow,QApplication,QStyleFactory,QHBoxLayout,QSplitter,QAction,QColor
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import os
import CodeEditor
from Assembler import Assembler6502
from emulator import Emulator6502
from PyQt5.QtCore import QThread

palette = [
      "#000000", "#ffffff", "#880000", "#aaffee",
      "#cc44cc", "#00cc55", "#0000aa", "#eeee77",
      "#dd8855", "#664400", "#ff7777", "#333333",
      "#777777", "#aaff66", "#0088ff", "#bbbbbb"
]
pixel_width = 8
pixel_x_num = 32
pixel_y_num = 32

UP = 1
RIGHT = 2
DOWN = 4
LEFT = 8

queue = Queue()


class CodeRunThread(QThread):
    def __init__(self,emulator,func):
        QThread.__init__(self)
        self.emulator = emulator
        self.func = func

    def __del__(self):
        self.wait()

    def stop(self):
        self.is_running = False

    def setBreakPoints(self,breakPoints):
        self.breakPoints = breakPoints

    def run(self):
        self.is_running = True
        while self.is_running:
            self.line,is_running = self.emulator.singleStep()
            if self.line in self.breakPoints:
                self.func()
                self.is_running = False
                break


# The new Stream Object which replaces the default stream associated with sys.stdout
# This object just puts data in a queue!

#class WriteStream(object):
#    def __init__(self,queue):
#        self.queue = queue
#
#    def write(self, text):
#        self.queue.put(text)

# A QObject (to be run in a QThread) which sits waiting for data to come through a Queue.Queue().
# It blocks until data is available, and one it has got something from the queue, it sends
# it to the "MainThread" by emitting a Qt Signal 

#class MyReceiver(QObject):
#    mysignal = pyqtSignal(str)
#
#    def __init__(self,queue,*args,**kwargs):
#        QObject.__init__(self,*args,**kwargs)
#        self.queue = queue
#        self.break_flag = 0
#
#    @pyqtSlot()
#    def run(self):
#        while True:
#            if self.break_flag:
#                break
#            else:
#                text = self.queue.get()
#                self.mysignal.emit(text)

class Screen(QWidget):
    def __init__(self):
        super(Screen, self).__init__()
        self.setupUi()
        self.white_mem_i = [4]
        self.mem = [0]*pixel_x_num*pixel_y_num
        self.direct = RIGHT

    def setupUi(self):
        self.setMinimumSize(pixel_width * pixel_x_num, pixel_width * pixel_y_num)

    def paintEvent(self,e=None):
        #self.mem = [0]*pixel_x_num*pixel_y_num
        #for i in self.white_mem_i:
        #    self.mem[i] = 1
        #for i in range(len(self.mem)):
        #    if self.mem[i] != 0:
        #        print(i,self.mem[i])
        qp = QPainter()
        qp.begin(self)
        self.draw(qp)
        qp.end()

    def draw(self,qp):
        for i in range(pixel_x_num * pixel_y_num):
            color = QColor(0, 0, 0)
            color.setNamedColor(palette[self.mem[i]&0xF])
            qp.setPen(color)
            qp.setBrush(color)
            qp.drawRect((i%pixel_x_num)*pixel_width, (i//pixel_y_num)*pixel_width, pixel_width, pixel_width)

    def assign_func(self,func):
        self.func = func

    def keyPressEvent(self, event):
        if type(event) == QKeyEvent:
            #here accept the event and do something
            event.accept()
            key = event.key()
            if(key == Qt.Key_W):
                print("Up key pressed")
                self.direct = UP
            elif(key == Qt.Key_D):
                print("Right key pressed")
                self.direct = RIGHT
            elif(key == Qt.Key_S):
                print("Down key pressed")
                self.direct = DOWN
            elif(key == Qt.Key_A):
                print("Left key pressed")
                self.direct = LEFT
            self.update()
            self.func(self.direct)
        else:
            event.ignore()        
    
    def mousePressEvent(self, event):
        self.setFocus()

class My6502(QWidget):
    def __init__(self):
        super(My6502, self).__init__()
        #self.my_receiver = MyReceiver(queue)
        #self.my_receiver.mysignal.connect(self.append_text)
        #self.thread1 = QThread()
        #self.my_receiver.moveToThread(self.thread1)
        #self.thread1.started.connect(self.my_receiver.run)
        #self.thread1.start()
        self.setupUi()

    def setText(self,text):
        self.textBox.setText(text)

    def getText(self):
        return self.textBox.text()


    def setupUi(self):
        # A vertical box layout
        #layout = QVBoxLayout()

         
        machineHBox = QHBoxLayout()

        self.textBox = CodeEditor.SimplePythonEditor()

        self.screen = Screen()

        machineHBox.addWidget(self.textBox,1)
        machineHBox.addWidget(self.screen)

        #splitterV.addWidget(machineHBox)

        #layout.addLayout(machineHBox,1)
         
        #memHBox = QHBoxLayout()
        #memHBox.addWidget(self.logBox)
        #layout.addLayout(memHBox)
        #layout.addWidget(splitterV)
        #layout.addWidget(self.logBox)

        self.setLayout(machineHBox)

    #@pyqtSlot(str)
    #def append_text(self,text):
    #    self.logBox.moveCursor(QTextCursor.End)
    #    self.logBox.insertPlainText( text )

class MainWidget(QWidget):
    def __init__(self):
        super(MainWidget, self).__init__()
        self.setupUi()

    def setupUi(self):
        #hbox = QHBoxLayout(self)
        layout = QHBoxLayout(self)
        splitterV = QSplitter(Qt.Vertical)
        self.my6502 = My6502()
        #font.setFamily(_fromUtf8("Monospace"))
        #self.logBox = QTextEdit()
        #self.logBox.setFont(font)
        self.console = tabs.ConsoleTabs();
        splitterV.addWidget(self.my6502)
        splitterV.addWidget(self.console)
        layout.addWidget(splitterV)
        self.setLayout(layout)

    def log(self,log):
        self.console.append(log)

class SearchText(QLineEdit):
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Return:
            self.trigerSearch(self.text())
        else:
            super(SearchText, self).keyPressEvent(event)

    def trigerSearch(self,text):
        self.search(text)

    def addSearch(self,search,parent):
        self.search = search
        self.parent = parent

class MainWindow(QMainWindow):        
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi()
        self.assembler = Assembler6502()
        self.emulator = Emulator6502()
        self.codeRun = CodeRunThread(self.emulator,self.moveLine)

    def setupUi(self):
        self.mainWidget = MainWidget()
        self.mainWidget.my6502.screen.assign_func(self.setDirect)
        self.setCentralWidget(self.mainWidget)
        self.setGeometry(80, 60, 1200, 640)

        newAction = QAction('New', self)
        newAction.setShortcut('Ctrl+N')
        newAction.setStatusTip('Create new file')
        newAction.triggered.connect(self.newFile)
        
        saveAction = QAction('Save', self)
        saveAction.setShortcut('Ctrl+S')
        saveAction.setStatusTip('Save current file')
        saveAction.triggered.connect(self.saveFile)
        
        openAction = QAction('Open', self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open a file')
        openAction.triggered.connect(self.openFile)
        
        closeAction = QAction('Exit', self)
        closeAction.setShortcut('Ctrl+Q')
        closeAction.setStatusTip('Exit')
        closeAction.triggered.connect(self.close)

        self.buildAction = QAction('Build',self)
        self.buildAction.setShortcut('Ctrl+B')
        self.buildAction.setStatusTip('Build')
        self.buildAction.triggered.connect(self.build)
        
        self.runAction = QAction('Run',self)
        self.runAction.setShortcut('Ctrl+R')
        self.runAction.setStatusTip('Run')
        self.runAction.triggered.connect(self.run)
        
        self.debugAction = QAction('Debug',self)
        self.debugAction.setShortcut('Ctrl+D')
        self.debugAction.setStatusTip('Debug')
        self.debugAction.triggered.connect(self.debug)
        
        self.debugNextAction = QAction('>',self)
        self.debugNextAction.setShortcut('Ctrl+D')
        self.debugNextAction.setStatusTip('next')
        self.debugNextAction.triggered.connect(self.debugNext)
        
        self.debugBreakAction = QAction('>>',self)
        self.debugBreakAction.setShortcut('Ctrl+D')
        self.debugBreakAction.setStatusTip('run to breakpoint')
        self.debugBreakAction.triggered.connect(self.debugBreak)


        self.searchText = SearchText()
        self.searchText.addSearch(self.search,self)
        self.searchText.setFixedWidth(100)

        #self.searchNextAction = QAction(u'\u25bc',self)
        #self.searchNextAction.setShortcut('Ctrl+D')
        #self.searchNextAction.setStatusTip('Next')
        #self.searchNextAction.triggered.connect(self.searchNext)

        #self.searchPrevAction = QAction(u'\u25b2',self)
        #self.searchPrevAction.setShortcut('Ctrl+D')
        #self.searchPrevAction.setStatusTip('Previous')
        #self.searchPrevAction.triggered.connect(self.searchPrev)

        searchWidget = QWidgetAction(self)
        searchWidget.setDefaultWidget(self.searchText)
        
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(newAction)
        fileMenu.addAction(openAction)
        fileMenu.addAction(saveAction)
        fileMenu.addAction(closeAction)


        toolbarFile = self.addToolBar('File')
        toolbarFile.addAction(newAction)
        toolbarFile.addAction(openAction)
        toolbarFile.addAction(saveAction)
        toolbarFile.addAction(closeAction)

        toolbarRun = self.addToolBar('Run')
        toolbarRun.addAction(self.buildAction)
        toolbarRun.addAction(self.runAction)
        toolbarRun.addAction(self.debugAction)
        toolbarRun.addAction(self.debugNextAction)
        toolbarRun.addAction(self.debugBreakAction)

        searchbarRun = self.addToolBar('Search')
        searchbarRun.addAction(searchWidget)

        self.openFile(os.path.join(os.getcwd(),os.path.pardir,"asm","snake.asm"))


    def newFile(self):
        self.mainWidget.my6502.textBox.clear()
        
    def saveFile(self):
        filename = QFileDialog.getSaveFileName(self, 'Save File', os.getcwd())
        if filename:
            with open(filename, 'w') as f:
                filedata = self.mainWidget.my6502.getText().toPlainText()
                f.write(filedata)
        
        
    def openFile(self,fn = None):
        if not fn:
            filename = QFileDialog.getOpenFileName(self, 'Open File', os.getcwd())
        else:
            filename = fn
        f = open(filename, 'r')
        filedata = f.read()
        self.mainWidget.my6502.setText(filedata)
        f.close()

    def build(self):
        self.assembler.assemble(self.mainWidget.my6502.getText())
        self.runAction.setEnabled(not self.assembler.build_fail)
        self.mainWidget.log(self.assembler.msg)

    def run(self):
        self.breakPoints = self.mainWidget.my6502.textBox.breakPoints
        self.emulator.init()
        self.emulator.load(self.assembler.last_codes,self.assembler.pc_line)
        self.codeRun.breakPoints = self.breakPoints
        self.codeRun.start()
        self.mainWidget.log(self.emulator.msg)

    def debug(self):
        self.breakPoints = self.mainWidget.my6502.textBox.breakPoints
        self.emulator.init()
        self.emulator.load(self.assembler.last_codes,self.assembler.pc_line)
        self.mainWidget.console.setMem(self.emulator.mem)
        self.line = self.emulator.reset()
        self.mainWidget.my6502.textBox.runLine = self.line
        self.mainWidget.my6502.textBox.markerAdd(self.line, self.mainWidget.my6502.textBox.ARROW_MARKER_NUM)
        self.loadWatchers()
        self.moveLine()

    def drawScreen(self):
        self.mainWidget.my6502.screen.mem = self.emulator.mem[0x200:0x600]
        #print self.mainWidget.my6502.screen.mem
        self.mainWidget.my6502.update()

    def setDirect(self,direct):
        self.emulator.mem[2] = direct


    def loadWatchers(self,fn = None):
        if not fn:
            fn = "watchers.txt"
        f = open(fn)
        lines = f.readlines()
        f.close()
        watchers = {}
        for line in lines:
            k,v = line.strip().split(':')
            k = k.strip()
            v = v.strip()
            watchers.update({k:v})
        self.mainWidget.console.loadWatchers(watchers)

    def saveWatchers(self):
        watchers = self.mainWidget.console.saveWatchers()
        s = ''
        for k,v in watchers.items():
            s += "%s:%02X\n"%(k,v)
        f = open("watchers.txt")
        f.write(s)
        f.close()
        
    def debugNext(self):
        self.line,is_running = self.emulator.singleStep()
        self.moveLine()
        
    def debugBreak(self):
        self.codeRun.breakPoints = self.breakPoints
        self.codeRun.start()

    def moveLine(self):
        self.mainWidget.my6502.textBox.move_cursor_to(self.line)
        self.runLine = self.line
        self.mainWidget.console.setMem(self.emulator.mem)
        self.mainWidget.console.updateWatchers()
        self.mainWidget.console.setPC(self.emulator.regPC)
        self.mainWidget.console.setSP(self.emulator.regSP)
        self.mainWidget.console.setRegA(self.emulator.regA)
        self.mainWidget.console.setRegX(self.emulator.regX)
        self.mainWidget.console.setRegY(self.emulator.regY)
        self.mainWidget.console.setFlagN(self.emulator.flagN)
        self.mainWidget.console.setFlagV(self.emulator.flagV)
        self.mainWidget.console.setFlagB(self.emulator.flagB)
        self.mainWidget.console.setFlagD(self.emulator.flagD)
        self.mainWidget.console.setFlagI(self.emulator.flagI)
        self.mainWidget.console.setFlagZ(self.emulator.flagZ)
        self.mainWidget.console.setFlagC(self.emulator.flagC)
        self.mainWidget.console.highlight(self.emulator.regPC)
        self.drawScreen()

    def search(self,text):
        if not self.mainWidget.my6502.textBox.findFirst(text,0,0,0,0):
            self.mainWidget.my6502.textBox.findFirst(text,0,0,0,0,line=0,index=0)
            #self.mainWidget.my6502.textBox.findNext()


    #def searchNext(self):
    #    self.mainWidget.my6502.textBox.findNext();
        
def main():
    #sys.stdout = WriteStream(queue)
    QApplication.setStyle(QStyleFactory.create('Cleanlooks'))
    app = QApplication(sys.argv)
    ex = MainWindow()
#    ex = Screen()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
